#! /usr/bin/env python

"""Top-level module for Astronomicon."""

import os
import sys
import time
import math
import random
import copy
random.seed()
import re

#Importing G initializes pygame
import G

import Config

import pygame
from pygame.locals import *

width = Config.width
height = Config.height

window = pygame.display.set_mode((width*5/4, height))
wwidth, wheight = window.get_rect().size

import Configurable
import Ship
import Util
import Weapon
import Behavior
import Formation
import Level
import Animation
import Graphics
import Text
import Effect
import Powerup
import Shield
import Generator
import Timekeeper
import HUD
import UI
import Keys
import Vector
import Campaign
import Wireframe
import Save
import Item

Graphics.init()

frame_rate = Config.frame_rate
scroll_rate = Config.scroll_rate
actioncheckinterval = Config.actioncheckinterval
expandradius = Config.screen_expand_dist
HUDinterval = Config.HUDinterval

iteminfodims = (400,40)

targetingreticle = (Graphics.targetingreticle[0], Vector.s_div(Graphics.targetingreticle[1].size,2))
lockedreticle = (Graphics.lockedreticle[0], Vector.s_div(Graphics.lockedreticle[1].size,2))
      
typing_font = Text.font(size=12)
midfont = Text.font(size=24)
menufont = Text.font(size=36)
defaultbuttonargs = {'maincolor': UI.defcolor,
                     'hovercolor': UI.hovercolor,
                     'presscolor': UI.presscolor
                    }
   
class Game(Timekeeper.Timekeeper):
   __slots__ = ('screen',
                'screenrect',
                'campaigns',
                'ingame',
                'background',
                'expanded_screen',
                'ships_left',
                'cur_formation',
                'last_formation_spawn',
                'next_formation_cond',
                'action_timer',
                'background_objects',
                'background_row_densities',
                'done',
                'next',
                'gameover',
                'paused',
                'HUD',
                'HUDtimer',
                'typed_text',
                'cur_level',
                'cur_level_index',
                'clock',
                'lastrepairdrop')
   
   def __init__(self):
      G.game = self
      self.window = window
      self.screen = pygame.Surface((width, height))
      self.screenrect = self.screen.get_rect()
      self.expanded_screen = self.screen.get_rect().inflate(2*expandradius, 2*expandradius)
      self.ships_left = 0
      self.action_timer = 0
      self.paused = False
      self.typed_text = ""
      self.ingame = False
      self.next = lambda: None
      self.clock = pygame.time.Clock()
      self.last_repair_drop = 0.0
      
      self.campaigns = Config.load_config(Campaign.Campaign)
   
   def backspace(self):
      if UI.focus is None or not UI.focus.typing:
         if self.typed_text != "":
            self.typed_text = self.typed_text[0:-1]
      else:
         UI.focus.backspace()
   
   def check_actions(self):
      self.action_timer += 1
      if self.action_timer > actioncheckinterval:
         for i, action in enumerate(G.action_queue):
            if action.update():
               del G.action_queue[i]
            self.action_timer -= actioncheckinterval
   
   def display_messages(self, screen):
      for i, mes in enumerate(G.messages):
         screen.blit(mes[0], mes[1])
         mes[2] -= self.frame_time
         if mes[2] < 0:
            del G.messages[i]
   
   def display_typing(self, screen):
      Graphics.blit_anchored(screen,
             typing_font.render(self.typed_text, 1, (255,255,200)),
             (20, height-30, 'bottomleft'))
   
   def draw_background(self):
      self.screen_scroll_ybuffer += scroll_rate * self.frame()
      scroll_y_t, self.screen_scroll_ybuffer = divmod(self.screen_scroll_ybuffer, 1.0)
      scroll_y = int(scroll_y_t)
      if scroll_y > 0:
         self.background.scroll(dy=scroll_y)
         for i, bd in enumerate(self.background_row_densities):
            num, rand = divmod(bd, 1.0)
            num = int(num + random.random() < rand)
            for j in range(0, num):
               x = random.randint(0, width-1)
               obj = pygame.sprite.Sprite(self.background_objects)
               obj.image = self.cur_level.backgroundobjects[i]
               obj.rect = obj.image.get_rect()
               obj.rect.bottomleft = (x,0)
      
      for sprite in self.background_objects.sprites():
         sprite.rect.move_ip(0, scroll_y)
         if sprite.rect.top >= 1:
            self.background.blit(sprite.image, sprite.rect.topleft)
            sprite.kill()
   
   def draw_HUD(self):
      if self.HUDtimer == 0:
         self.HUD.draw()
      self.HUDtimer = (self.HUDtimer + 1) % HUDinterval
   
   def drawinterlevelscreen(self):
      G.UI.empty()
      nextlevel = UI.Label(self.cur_campaign.levelfiles[self.cur_level_index], 
                           (wwidth/5, wheight/24), fontsize=36)
      briefing = UI.Scroller.fromobject(
         UI.TextBox(self.cur_level.description, (0,0), wwidth/4-UI.defscrollbarwidth, fontsize=12, background=(0,0,0)),
         (wwidth/5, wheight/4), (wwidth/4, wheight/2))
      
      
      buttonargs = (('Shop', self.drawshopscreen),
                    ('Save', self.drawsavescreen),
                    ('Abandon', lambda: self.endloop(self.menuloop)),
                    ('Fly', lambda: self.endloop(self.ingameloop))
                   )
      
      UI.generatetextbuttons(buttonargs, menufont, wwidth/2, wheight/4, wheight/10, **defaultbuttonargs)
   
   def drawkeybindingscreen(self, back=None):
      if back is None:
         back = self.drawoptionsscreen
      G.UI.empty()
      title = UI.Label('Adjust Keybindings', (wwidth/2, wheight/24), fontsize=48,
                       anchor='top')
      
      keynames = list()
      for v in Keys.keynames.values():
         if v not in keynames:
            keynames.append(v)
      keynames.sort()
      
      width = 2*wwidth/3
      font = Text.font(size=20)
      height = 0
      for name in keynames:
         height += font.size(name)[1]
      pane = pygame.Surface((width, height))
      y = 0
      buttons = list()
      for name in keynames:
         button = UI.KeyMapButton(name, font, (0, y), width, buttons, group=None)
         buttons.append(button)
         y += button.height
      scroller = UI.Scroller(pane, (wwidth/6, wheight/5-30), (width+UI.defscrollbarwidth, 2*wheight/3))
      for button in buttons:
         scroller.embeddedobjects.add(button)
      
      def accept():
         Keys.save_changes()
         back()
      cancelbutton = UI.TextButton("Cancel", menufont, (wwidth/4, 7*wheight/8),
                                 background=(0,0,0),
                                 function=back)
      
      acceptbutton = UI.TextButton("Accept", menufont, (3*wwidth/4, 7*wheight/8),
                                 background=(0,0,0),
                                 function=accept)
   
   def drawloadscreen(self, back=None):
      if back is None:
         if self.paused:
            back = self.drawpausemenu
         else:
            back = self.drawmainmenu
      G.UI.empty()
      title = UI.Label('Load Game', (wwidth/2, wheight/24), fontsize=48,
                       anchor='top')
      
      savegames = os.listdir(os.path.join(os.curdir, 'Save'))
      savegames = filter(lambda x: x.endswith('.save') and x != 'Autosave.save', savegames)
      
      buttonimages = list()
      for s in savegames:
         buttonimages.append(midfont.render(s.split('.')[0], 1, (255,255,255), (0,0,0)))
      
      sgroup = pygame.sprite.Group()
      bl, buttonwindow = UI.ListBox(buttonimages, heightmult=1.1, group=sgroup)
      
      scroller = UI.Scroller(buttonwindow, (wwidth/4, wheight/6), (wwidth/2, wheight/2), anchor='topleft')
      scroller.embeddedobjects = sgroup
      
      UI.TextButton("Back", menufont, (wwidth/4, 7*wheight/8), background=(0,0,0), anchor='center', function=back)
      
      def delete():
         if bl.selected() != -1:
            s = os.path.join('Save', savegames[bl.selected()])
            if os.path.exists(s):
               os.remove(s)
               self.drawloadscreen(back)
               
      UI.TextButton("Delete", menufont, (wwidth/2, 7*wheight/8), background=(0,0,0), anchor='center', function=delete)
      
      def load():
         filename = savegames[bl.selected()]
         self.load_saved_game(filename)
      
      UI.TextButton("Load", menufont, (3*wwidth/4, 7*wheight/8), background=(0,0,0), anchor='center', function=load)
   
   def drawmainmenu(self):
      G.UI.empty()
      title = UI.Label('Astronomicon', (wwidth/2, wheight/24), fontsize=60,
                       anchor='top')
      
      buttonargs = (('Start', self.startgamemenu),
                    ('Continue', self.load_saved_game),
                    ('Load', self.drawloadscreen),
                    ('Options', self.drawoptionsscreen),
                    ('Exit', self.exit)
                   )
      
      UI.generatetextbuttons(buttonargs, menufont, wwidth/2, wheight/4, wheight/10, anchor='center', **defaultbuttonargs)
   
   def drawoptionsscreen(self, back=None):
      if back is None:
         if self.paused:
            back = self.drawpausemenu
         else:
            back = self.drawmainmenu
      G.UI.empty()
      
      title = UI.Label('Options', (wwidth/2, wheight/24), fontsize=48,
                       anchor='top')
      
      buttonargs = (('Controls', self.drawkeybindingscreen),
                   )
      
      UI.generatetextbuttons(buttonargs, menufont, wwidth/2, wheight/4, wheight/10, anchor='center', **defaultbuttonargs)
      
      UI.TextButton("Back", menufont, (wwidth/2, 7*wheight/8), background=(0,0,0), anchor='center', function=back)
   
   def drawpausemenu(self):
      G.UI.empty()
      title = UI.Label('Paused', (wwidth/2, wheight/24), fontsize=48,
                       anchor='top')
      
      buttonargs = (('Options', self.drawoptionsscreen),
                    ('Save', self.drawsavescreen),
                    ('Quit to Menu', self.quit),
                    ('Return', self.pause)
                   )
      
      UI.generatetextbuttons(buttonargs, menufont, wwidth/2, wheight/4, wheight/10, anchor='center', **defaultbuttonargs)
   
   def drawsavescreen(self, back=None):
      if back is None:
         if self.paused:
            back = self.drawpausemenu
         else:
            back = self.drawinterlevelscreen
      G.UI.empty()
      title = UI.Label('Save Game', (wwidth/2, wheight/24), fontsize=48,
                       anchor='top')
      
      entry = UI.Entry("", (wwidth/4, 7*wheight/8-75), wwidth/2, fontsize=24, background=(0,0,0))
      
      savegames = os.listdir(os.path.join(os.curdir, 'Save'))
      savegames = filter(lambda x: x.endswith('.save') and x != 'Autosave.save', savegames)
      savegames.sort()
      
      buttonimages = list()
      for s in savegames:
         buttonimages.append(midfont.render(s.split('.')[0], 1, (255,255,255), (0,0,0)))
      
      sgroup = pygame.sprite.Group()
      bl, buttonwindow = UI.ListBox(buttonimages, heightmult=1.1, group=sgroup)
      
      for i, b in enumerate(bl.buttons):
         b.function = lambda x=i: entry.settext(savegames[x].split('.')[0])
      
      scroller = UI.Scroller(buttonwindow, (wwidth/4, wheight/6), (wwidth/2, wheight/2-75), anchor='topleft')
      scroller.embeddedobjects = sgroup
      
      UI.TextButton("Back", menufont, (wwidth/4, 7*wheight/8), background=(0,0,0), anchor='center', function=back)
      
      def delete():
         if bl.selected() != -1:
            s = os.path.join('Save', savegames[bl.selected()])
            if os.path.exists(s):
               os.remove(s)
               self.drawsavescreen(back)
               
      UI.TextButton("Delete", menufont, (wwidth/2, 7*wheight/8), background=(0,0,0), anchor='center', function=delete)
      
      def save():
         s = entry.text
         if not s.endswith('.save'):
            s = "%s.save" % s.split('.')[0]
         self.save_game(s)
         if s not in savegames:
            self.drawsavescreen(back)
      
      UI.TextButton("Save", menufont, (3*wwidth/4, 7*wheight/8), background=(0,0,0), anchor='center', function=save)
   
   def drawshopscreen(self):
      G.UI.empty()
      moneylabel = UI.Label("%s" % str(G.player_ship.money).zfill(15), (5*wwidth/12, wheight/12), HUD.moneycolor, fontsize=24, background=(0,0,0), anchor='topright')
      
      mlabel = [moneylabel]
      
      def updatemoney():
         mlabel[0].kill()
         mlabel[0] = UI.Label("%s" % str(G.player_ship.money).zfill(15), (5*wwidth/12, wheight/12), HUD.moneycolor, fontsize=24, background=(0,0,0), anchor='topright')
      
      dims = (wwidth/6, wheight/2)
      subwindow = pygame.Surface(dims)
      scroller = UI.Scroller(subwindow, (wwidth/2-10, wheight/6), dims, anchor='topright')
      
      dummyship = Ship.Dummy_Ship.from_ship(G.player_ship, subwindow)

      changed_w = [None, -1]

      def equip_dummyship():
         original_weapons = [w for w in G.player_ship.weapons]
         for i, w in enumerate(original_weapons):
            if w is not None:
               original_weapons[i] = w.copy()
               dummyship.equip_weapon((original_weapons[i], i))
         changed_w[0] = None
         changed_w[1] = -1

      equip_dummyship()
      dummyship.rect.center = (dims[0]/2, 2*dims[1]/3)
      scroller.embeddedsprites.add(dummyship)
      
      wireframe = Wireframe.Wireframe(G.player_ship, (wwidth/4, wheight/4), color=(0,255,0), show_ammo=False, pad=(20,40))
      wireframe.rect.bottomleft = (10, wheight/2)
      G.UI.add(wireframe)
      
      realitems = list()
      buttonimages = list()

      playeritems = [w.name for w in G.player_ship.weapons if (w is not None and w.name not in self.cur_level.shopitems)]
      if G.player_ship.shield is not None and G.player_ship.shield.name not in self.cur_level.shopitems:
         playeritems.append(G.player_ship.shield.name)
      if G.player_ship.generator is not None and G.player_ship.generator.name not in self.cur_level.shopitems:
         playeritems.append(G.player_ship.generator.name)

      shopweapons = list()
      shopshields = list()
      shopgens = list()
      for item in self.cur_level.shopitems + playeritems:
         if G.items.has_key(item):
            ri = G.items[item]
            if ri['type'] is Weapon.Weapon:
               shopweapons.append(ri)
            elif ri['type'] is Shield.Shield:
               shopshields.append(ri)
            elif ri['type'] is Generator.Generator:
               shopgens.append(ri)

      def sortkey(x):
         if x['type'] is Weapon.Weapon:
            v = x.get('value', 0)
            if type(v) is list:
               return v[-1]
            else:
               return v
         else:
            return x.get('value', 0)
            
      for l in (shopweapons, shopshields, shopgens):
         l.sort(key=sortkey)
      
      realitems = shopweapons + shopshields + shopgens
      for ri in realitems:
         buttonimages.append(self.itemstatimage(ri))
      
      sgroup = pygame.sprite.Group()
      buttonlist, buttonwindow = UI.ListBox(buttonimages, group=sgroup, widthmult=1.0, heightmult=1.0)
      
      if len(realitems) > 0:
         cur_item = [realitems[0]]
         max_weapon_level = max([item.get('maxlevels', 1) for item in realitems]) - 1
      else:
         cur_item = None
         max_weapon_level = 0
      cur_weapon_level = [0]
      #Current preferred weaponpoint, index of it
      pref_weaponpoint = [0,0]
      poss_weaponpoints = [list()]

      def set_possible_weaponpoints(weight):
         poss_weaponpoints[0] = [i for i, x in enumerate(G.player_ship.weaponpoints) if x[2] >= weight]

      def set_preferred_weaponpoint(i=0, delta=None):
         if delta is not None:
            i = (pref_weaponpoint[1] + delta) % len(poss_weaponpoints[0])
         pref_weaponpoint[1] = i
         if len(poss_weaponpoints[0]) > 0:
            pref_weaponpoint[0] = poss_weaponpoints[0][pref_weaponpoint[1]]
         else:
            pref_weaponpoint[0] = None
      
      levelindicator = UI.TextBox("Level %d" % cur_weapon_level[0],
                                  (wwidth/8+50, 2*wheight/3+60), wwidth/8+30,
                                  fontsize=36, background=(0,0,0), anchor='left')
         
      levelbuttons = [None, None]
      
      def setlevel(new_level):
         cur_weapon_level[0] = Util.constrain(new_level, 0, max_weapon_level)
         updatepreview()
      
      for i, x in enumerate((-1, 1)):
         images = [pygame.Surface((80, 80)) for q in range(0, 3)]
         for j, color in enumerate((UI.defcolor, UI.hovercolor, UI.presscolor)):
            pygame.draw.polygon(images[j], color, ((50 - 30*x, 20),
                                                  (50 + 30*x, 50),
                                                  (50 - 30*x, 80)))
         levelbuttons[i] = UI.Button(images[0], (wwidth/4 + wwidth*x/8, 2*wheight/3+10),
                                     function=lambda d=x: setlevel(cur_weapon_level[0] + d),
                                     anchor='top', hoverimage=images[1], pressimage=images[2])
      
      desctext = UI.TextBox("", (wwidth/2+10, wheight/2+25), wwidth/2-50, fontsize=12, background=(0,0,0))
      #UI.Scroller.fromobject(desctext, (wwidth/2+10, 2*wheight/3), (wwidth/2-50, wheight/6))
      
      def updatepreview(i=None):
         if changed_w[1] != -1:
            dummyship.weapons[changed_w[1]] = None
            if changed_w[0] is not None:
               dummyship.equip_weapon((Configurable.instantiate(changed_w[0]), changed_w[1]))
         if cur_item is None:
            return
         if i is not None:
            cur_item[0] = realitems[i]
         else:
            i = realitems.index(cur_item[0])
         item = cur_item[0]
         if item.get('type', '') == Weapon.Weapon:
            level = min(cur_weapon_level[0], item.get('maxlevels', 1)-1)
            w = Configurable.instantiate(item, level=level)
            if item.has_key('alternate'):
               if item['alternate']:
                  w.start_alternate()
               else:
                  w.stop_alternate()
            set_possible_weaponpoints(w.lattr('weight'))
            if len(poss_weaponpoints[0]) > 0:
               if pref_weaponpoint[1] > len(poss_weaponpoints[0]):
                  set_preferred_weaponpoint()
               elif pref_weaponpoint[0] in poss_weaponpoints[0]:
                  pref_weaponpoint[1] = poss_weaponpoints[0].index(pref_weaponpoint[0])
               while G.player_ship.weaponpoints[pref_weaponpoint[0]][2] < w.lattr('weight'):
                  set_preferred_weaponpoint(delta=1)
               wp = pref_weaponpoint[0]
            else:
               wp = None
            if wp is not None:
               prev = dummyship.equip_weapon((w, wp))
            else:
               prev = dummyship.equip_item(w)
            if prev is not None:
               changed_w[0] = copy.deepcopy(G.items[prev.name])
               changed_w[0]['level'] = prev.level
            else:
               changed_w[0] = prev
            if prev is not None:
               changed_w[1] = prev.weaponpoint
            elif w.weaponpoint is not None:
               changed_w[1] = w.weaponpoint
            else:
               changed_w[1] = -1
            if w.weaponpoint is not None:
               wireframe.highlightweaponpoint(w.weaponpoint)
               wireframe.updateweapons()
               desctext.settext(w.augmented_description())
            else:
               wireframe.highlightweaponpoint(None)
         else:
            wireframe.highlightweaponpoint(None)
            level = None
            w = Configurable.instantiate(item)
            prev = dummyship.equip_item(w)
            desctext.settext(w.augmented_description())
         buttonlist.buttons[i].change_image(self.itemstatimage(w))
         if level is not None:
            levelindicator.settext("Level %d" % level)
         else:
            levelindicator.settext("")

      def toggleweaponpoint():
         if len(poss_weaponpoints[0]) > 0:
            set_preferred_weaponpoint(delta=1)
            updatepreview()

      wpbutton = UI.Button(pygame.Surface((0,0)), (0,0), function=toggleweaponpoint)
      wpbutton.rect.size = (wwidth/4, wheight/4)
      wpbutton.rect.topleft = wireframe.rect.topleft
      
      for i, button in enumerate(buttonlist.buttons):
         button.function = lambda x=i: updatepreview(x)
      
      scroller = UI.Scroller(buttonwindow, (wwidth/2+10, 20), (wwidth/2-20, wheight/2))
      scroller.embeddedobjects = sgroup
      
      def buy():
         if buttonlist.selected() == -1:
            return
         item = realitems[buttonlist.selected()]
         if item.get('type', '') == Weapon.Weapon:
            level = min(cur_weapon_level[0], item.get('maxlevels', 1)-1)
            realitem = Configurable.instantiate(item, level=level)
            w = realitem.lattr('weight')
            maxsize = max([wp[2] for wp in G.player_ship.weaponpoints])
            if w > maxsize:
               print "Weapon is too heavy."
               return
         else:
            realitem = Configurable.instantiate(item)
         cost = realitem.get_value()
         wp = pref_weaponpoint[0]
         if isinstance(realitem, Weapon.Weapon):
            prev = G.player_ship.equip_weapon((realitem, wp), do_equip=False)
         else:
            prev = G.player_ship.equip_item(realitem, do_equip=False)
         if prev is not None:
            prev_value = prev.get_value()
         else:
            prev_value = 0
         if cost - prev_value <= G.player_ship.money:
            if isinstance(realitem, Weapon.Weapon):
               prev = G.player_ship.equip_weapon((realitem, wp))
            else:
               prev = G.player_ship.equip_item(realitem)
            G.player_ship.money -= (cost - prev_value)
            updatemoney()
            wireframe.updateweapons()
            wireframe.update()
            equip_dummyship()
            updatepreview()
      
      buybutton = UI.TextButton("Buy", menufont, (wwidth/4, 7*wheight/8),
                                 background=(0,0,0),
                                 function=buy)
      
      def quit():
         dummyship.kill()
         self.drawinterlevelscreen()
      
      quitbutton = UI.TextButton("Return", menufont, (3*wwidth/4, 7*wheight/8),
                                 background=(0,0,0),
                                 function=quit)
      
      updatepreview()
   
   def draw_reticle(self, ship):
      if ship.has_homing_weapon and ship.target is not None:
         if not ship.locked:
            reticle = targetingreticle
         else:
            reticle = lockedreticle
         self.screen.blit(reticle[0], 
                          (ship.target.rect.centerx - reticle[1][0],
                           ship.target.rect.centery - reticle[1][1]))

   def endlevel(self):
      if self.cur_level_index >= len(self.cur_campaign.levelfiles):
         self.quit()
      else:
         self.endloop(self.interlevelloop)
   
   def endloop(self, next=lambda: None):
      self.done = True
      self.next = next
   
   def evaluate(self, text):
      eval(text)
  
   def exit(self):
      G.savesettings()
      sys.exit(0)
   
   def get_typed_text(self):
      return self.typed_text
   
   def ingameloop(self):
      self.ingame = True
      self.gameover = False
      self.done = False
      self.init_background()
      self.HUDtimer = 0
      self.screen_scroll_ybuffer = 0.0
      pygame.display.flip()
      
      ship = G.player_ship
      ship.refresh()
      self.HUD = HUD.HUD(self.window, self.screen, ship)
      Keys.init()
      
      Timekeeper.Timekeeper.start(self)
      Timekeeper.Timekeeper.frame_time = 1.0 / frame_rate
      
      groups = (self.background_objects,
               G.friendly_ships,
               G.enemy_ships,
               G.projectiles,
               G.powerups,
               G.effect_sprites,
               G.shields)
      windowgroups = (G.UI,
                      G.HUD)
      self.ships_left = 0
      self.next_formation()
               
      while not self.done:
         self.clock.tick(frame_rate)
         
         #Process events
         self.process_events()
         
         if not self.paused:      
            self.check_actions()
            #Check next wave
            if self.ready_next_formation():
               self.next_formation()
            
            #Make and blit background
            self.draw_background()
                  
            #Update groups
            map(lambda x: x.update(), groups)
            map(lambda x: x.update(), windowgroups)
            
            #Draw HUD if its interval has ticked down
            self.draw_HUD()
         else:
            G.UI.update()
            
         Timekeeper.Timekeeper.update(self)
            
         
         if not self.paused:
            self.screen.blit(self.background, (0,0))
         
            G.effects.update()
            #Draw sprites
            map(lambda x: x.draw(self.screen), groups)
            map(lambda x: x.draw(self.window), windowgroups)
         else:
            self.window.fill((0,0,0))
            G.UI.draw(self.window)
         
         #Print any temporary messages
         self.display_messages(self.screen)
         
         #Display typed text
         self.display_typing(self.screen)
         
         if not self.paused:
            #Display targeting reticle if applicable
            self.draw_reticle(ship)
         
            #Blit screen onto window
            self.window.blit(self.screen, (0,0))
         
         #Display screen
         pygame.display.flip()
   
      #Cleanup
      map(lambda x: x.empty(), groups)
      map(lambda x: x.empty(), windowgroups)
      G.effects.empty()
      G.messages = list()
      self.window.fill((0,0,0))
   
   def interlevelloop(self, save=True):
      if save:
         self.save_game()
      self.loadlevel()
      self.drawinterlevelscreen()
      
      self.menuinit()

      Keys.init()
      
      groups = (G.UI,)
      
      while not self.done:
         self.clock.tick(frame_rate/2+1)
         
         #Process events
         self.process_events()
         
         G.effects.update()
             
         self.check_actions()
               
         #Update groups
         map(lambda x: x.update(), groups)
         
         Timekeeper.Timekeeper.update(self)
            
         self.window.fill((0,0,0))
         #Draw sprites
         map(lambda x: x.draw(self.window), groups)
         #Print any temporary messages
         self.display_messages(self.window)
         #Display typed text
         self.display_typing(self.window)
         #Display screen
         pygame.display.flip()
      
      #Cleanup
      map(lambda x: x.empty(),  groups)
      G.messages = list()
      self.window.fill((0,0,0))
   
   def init_background(self):
      size = self.screen.get_size()
      self.background = pygame.Surface(size)
      self.background = self.background.convert()
      self.background.fill((0,0,0))
      random.seed()
      self.background_objects = pygame.sprite.Group()
      self.background_row_densities = [width * d for d in self.cur_level.backgrounddensities]
      for i, bd in enumerate(self.background_row_densities):
         num = int(bd * height)
         if random.random() < (bd % 1):
            num += 1
         for j in range(0, num):
            x = random.randint(0, width-1)
            y = random.randint(1, height-1)
            self.background.blit(self.cur_level.backgroundobjects[i], (x,y))
      self.screen.blit(self.background, (0,0))
   
   def itemstatimage(self, item, level=0):
      image = pygame.Surface(iteminfodims)
      if isinstance(item, Item.Item):
            l = midfont.render(item.name, 1, (255,255,255), (0,0,0))
            icon = item.get_icon()
            if icon is not None:
               iconr = icon.get_rect()
               Graphics.blit_anchored(image, icon, (0,iteminfodims[1]/2), 'left')
               iconx = iconr.width + 10
            else:
               iconx = 0
      else:
         if item.has_key('type'):
            l = midfont.render(item['name'], 1, (255,255,255), (0,0,0))
            if 'iconpath' in item:
               if isinstance(item['iconpath'], str):
                  ip = item['iconpath']
               else: 
                  ip = item['iconpath'][0]
               icon, iconr = Util.load_image(ip)
               Graphics.blit_anchored(image, icon, (0,iteminfodims[1]/2), 'left')
               iconx = iconr.width + 10
            else:
               iconx = 0
      Graphics.blit_anchored(image, l, (iconx,iteminfodims[1]/2), 'left')
      return image
   
   def load_saved_game(self, filename=Config.autosaveloc):
      filename = os.path.join('Save', filename)
      if os.path.exists(filename):
         save = Save.Save.load(filename)
         self.cur_campaign = Configurable.instantiate(self.campaigns[save.campaign])
         self.cur_level_index = save.levelindex
         G.player_ship = save.ship
         self.endloop(lambda: self.interlevelloop(save=False))
   
   def loadlevel(self, index=None):
      if index is None:
         index = self.cur_level_index
      else:
         self.cur_level_index = index
      levelfile = os.path.join(os.curdir,
                               Config.configdir,
                               Config.leveldir,
                               "%s.config" % self.cur_campaign.levelfiles[index])
      self.cur_level = Configurable.instantiate(Level.Level.many_from_file(levelfile))
      G.player_ship.HP = G.player_ship.health
      G.player_ship.shield.HP = G.player_ship.shield.health
      G.player_ship.rect.center = (width/2, 3*height/4)
   
   def lose(self):
      Text.message("Game over.", (200,200), 5.0,
                   Text.font(size=48), (255,128,128), True, True)
      self.gameover = True
      Util.after(self.endlevel, 5.0)
   
   def menuinit(self, frameratemult=2.0):
      self.paused = False
      self.ingame = False
      self.done = False
      pygame.display.flip()
      
      Keys.init()
      
      Timekeeper.Timekeeper.start(self)
      Timekeeper.Timekeeper.frame_time = frameratemult / frame_rate
   
   def menuloop(self):
      self.drawmainmenu()
      
      self.menuinit()
      
      groups = (G.UI,)
      
      while not self.done:
         self.clock.tick(frame_rate)
         
         #Process events
         self.process_events()
             
         self.check_actions()
               
         #Update groups
         map(lambda x: x.update(), groups)
         
         Timekeeper.Timekeeper.update(self)
            
         self.window.fill((0,0,0))
         #Draw sprites
         map(lambda x: x.draw(self.window), groups)
         #Print any temporary messages
         self.display_messages(self.window)
         #Display typed text
         self.display_typing(self.window)
         #Display screen
         pygame.display.flip()
      
      #Cleanup
      map(lambda x: x.empty(), groups)
      G.messages = list()
      self.window.fill((0,0,0))
         
   def next_formation(self):
      next_formation = self.cur_level.next_formation()
      if next_formation is not None:
         next_formation.spawn()
         self.last_formation_spawn = time.time()
         self.ships_left += next_formation.count
      elif not self.gameover:
         self.win()
   
   def pause(self):
      if self.paused:
         self.paused = False
         G.UI.empty()
         #ft = Timekeeper.Timekeeper.frame_time
         #Timekeeper.Timekeeper.update(self)
         #Timekeeper.Timekeeper.frame_time = ft
      else:
         self.paused = True
         self.drawpausemenu()
   
   def process_events(self):
      for event in pygame.event.get():
            if event.type == QUIT:
               self.exit()
            elif event.type == KEYDOWN:
               Keys.press(event.key)
            elif event.type == KEYUP:
               Keys.release(event.key)
            elif event.type == MOUSEBUTTONDOWN:
               UI.click(event.button)
            elif event.type == MOUSEBUTTONUP:
               UI.release(event.button)
   
   def quit(self):
      if self.ingame:
         self.endloop(self.menuloop)
   
   def ready_next_formation(self):
      if self.cur_level.formation_number >= len(self.cur_level.formations) - 1:
         return self.ships_left == 0
      else:
         if isinstance(self.cur_level.next_formation_cond(), int):
            return self.ships_left <= self.cur_level.next_formation_cond()
         else:
            return time.time() - self.last_formation_spawn >= self.cur_level.next_formation_cond()
   
   def run(self):
      pygame.display.set_caption('Astronomicon')
      self.menuloop()
      while True:
         self.next()
   
   def save_game(self, filename=Config.autosaveloc):
      filename = os.path.join('Save', filename)
      save = Save.Save(self.cur_campaign, self.cur_level_index, G.player_ship)
      save.save(filename)
   
   def set_typed_text(self, text):
      self.typed_text = text
   
   def startcampaign(self, campaign):
      self.cur_campaign = Configurable.instantiate(self.campaigns[campaign])
      self.cur_level_index = 0
      G.player_ship = Ship.spawn(G.ships[self.cur_campaign.startingship], (width/2, 3*height/4), friendly=True)
      G.player_ship.money = self.cur_campaign.startingmoney
      if self.cur_campaign.startingweaponsets is not None:
         if Util.maxdepth(self.cur_campaign.startingweaponsets) == 1:
            ws = [self.cur_campaign.startingweaponsets]
         else:
            ws = self.cur_campaign.startingweaponsets
         for w in ws:
            if len(w) > 1:
               weapon = [Configurable.instantiate(G.items[w[0]])]
               G.player_ship.equip_weapon(weapon + w[1:])
            else:
               G.player_ship.equip_item(Configurable.instantiate(G.items[w[0]]))
      if self.cur_campaign.startingshield is not None:
         G.player_ship.equip_item(Configurable.instantiate(G.items[self.cur_campaign.startingshield]))
      if self.cur_campaign.startinggenerator is not None:
         G.player_ship.equip_item(Configurable.instantiate(G.items[self.cur_campaign.startinggenerator]))
      self.endloop(self.interlevelloop)
   
   def startgamemenu(self):
      G.UI.empty()
      
      campaignlist = sorted(self.campaigns.keys())
      
      title = UI.Label('Select campaign', (wwidth/2, wheight/24), fontsize=48,
                       anchor='top')
      
      descbox = UI.TextBox("", (wwidth/2+10, wheight/6), wwidth/3, background=(0,0,0), fontsize=16)
      
      optionimages = list()
      for campaign in campaignlist:
         optionimages.append(midfont.render(campaign, 1, (255,255,255), (0,0,0)))
      sgroup = pygame.sprite.Group()
      buttonlist, subwindow = UI.ListBox(optionimages, group=sgroup)
      
      for button in buttonlist.buttons:
         button.function = lambda: descbox.settext(self.campaigns[campaignlist[buttonlist.selected()]].get('description', ''))
      
      scroller = UI.Scroller(subwindow, (wwidth/2-10, wheight/6), (wwidth/2, wheight/2),
                             anchor='topright')
      scroller.embeddedobjects = sgroup
      
      def start():
         s = buttonlist.selected()
         if s != -1:
            self.startcampaign(campaignlist[buttonlist.selected()])
      
      UI.TextButton('Start', midfont, (wwidth/3, 3*wheight/4), 
         anchor='top', function=start, **defaultbuttonargs)
      UI.TextButton('Cancel', midfont, (2*wwidth/3, 3*wheight/4), 
         anchor='top', function=lambda: self.endloop(self.menuloop), **defaultbuttonargs)
   
   def type(self, key):
      if UI.focus is None or not UI.focus.typing:
         self.set_typed_text("%s%s" % (self.typed_text, key))
      else:
         UI.focus.type(key)

   def win(self):
      Text.message("Congratulations, you won!", (200,200), 5.0,
                   Text.font(size=36), (128,255,128), True, True)
      self.gameover = True
      self.cur_level_index += 1
      Util.after(self.endlevel, 5.0)

game = Game()

def main():
   game.run()

if __name__ == '__main__':
   main()
